Tutorial: Generating and handling ensemble data

Introduction to population ensembles

In binary_c one can add up stellar population data very easily. This is usually done in something called a population ensemble, which - if you are familiar to Python or Perl - is just an associative array, called a dict in Python or hash in Perl. The libcdict library performs such tasks in native C in binary_c, so is extremely fast and sufficiently flexible for our needs.

The code to generate ensemble data is already set up in the src/ensemble/ directory of binary_c. You can either use one of the pre-existing sets of ensemble-logging code, or write your own and put it in src/ensemble/ensemble_log.c, or attach it through the extra_ensemble binary_c function hook.

To select the data that go into an ensemble, the ensemble_log() function applies a series of tests every timestep.

Data are stored in the ensemble every so often: you can choose whether this output uses linear or logarithmic timesteps. You probably do not want the timestep to be too short because if is it your dataset will be overwhelmingly large.

Please be careful when defining your own ensemble-logging functionality. You almost certainly want to bin your data appropriately using binary_c’s Bin_data(value,binwidth) macro. Please see src/ensemble/ensemble_log.c for many examples.

You can find much more detailed explanations of ensembles in the binary_c documentation available at https://binary_c.gitlab.io/ in The binary_c ensemble section.

How to generate ensemble data

Ensemble data is generated from the properties of a population of stars, so first let’s set up such a population.

The resolution, r, is the number of stars along each side of a three-dimensional M1 x M2 x orbital period parameter space. You should increase r to match your CPU power and patience.

You can set binaries to False to run only single stars.

Stars are sampled according to probabilities computed from the distributions of Moe and di Stefano (2017). We sample in equal steps of log(M1) using the M1samplerfunc variable. This is a string of code that is executed by the population.Moe_di_Stefano_2017() setup function.

[1]:
import os
import json

from binarycpython.utils.custom_logging_functions import temp_dir
from binarycpython import Population

TMP_DIR = temp_dir("notebooks", "notebook_ensembles", clean_path=True)

data_dir = os.path.join(TMP_DIR, 'data_dir')
os.makedirs(data_dir, exist_ok=True)

population = Population(tmp_dir=TMP_DIR, verbosity=2)
[2]:
mmin = population.minimum_stellar_mass()
mmax = 80.0 # Msun
binaries = True

r = 4 # resolution
resolution = {
    'M_1': r, # resolution in M1
    'q' : r, # resolution in M2
    'per': r, # resolution in orbital period
    'ecc' : 0 # resolution in eccentricity
}

############################################################
# Set population options
#
population.set(
    # Grid configuration
    max_evolution_time=15000, # 15000 Myr
    combine_ensemble_with_thread_joining=True, # always combine results in final thread
    save_ensemble_chunks = False, # we don't want to save the data to files
    minimum_timestep=1e-6, # 1e-6 Myr == 1 yr
    log_runtime_systems=0, # do not log systems (can be slow)
    verbosity=1, # 1 = some verbosity
    run_zero_probability_system=0, # skip zero-probability systems
)

# set the M1 sampling function
M1samplerfunc="self.const_linear(math.log({min}), math.log({max}), {res})".format(
        min = mmin,
        max = mmax,
        res = resolution["M_1"]
)

# set the resolutions to be used
Moe_resolutions = {
    "M": [resolution['M_1'], resolution['q'], 0, 0], # NB 0 for M1 means automatic
    "logP": [resolution['per'], 0, 0],
    "ecc": [resolution['ecc'], 0, 0] # 0 means no eccentricity distribution
}

# set the options for the Moe and di Stefano (2017) function
moe_options = {
        "normalize_multiplicities": "merge",
        "multiplicity": 2 if binaries else 1,
        "multiplicity_modulator": [1, 1 if binaries else 0, 0, 0],
        "resolutions": Moe_resolutions,
        'Mmin':mmin,
        "ranges": {
            "M": [
                mmin,
                mmax
            ],
            'logP': [ 0.0, 8.0 ] # 0 < log_10(P) < 8
        },
        'samplerfuncs' : {
            'M' : [
                M1samplerfunc,
                None,
                None,
                None
                ]
            }
    }

population.Moe_di_Stefano_2017(
        options=moe_options
    )
adding: max_evolution_time=15000 to BSE_options
adding: combine_ensemble_with_thread_joining=True to population_options
adding: save_ensemble_chunks=False to population_options
adding: minimum_timestep=1e-06 to BSE_options
adding: log_runtime_systems=0 to population_options
adding: verbosity=1 to population_options
Added sampling variable: {
    "name": "multiplicity",
    "parameter_name": "multiplicity",
    "longname": "multiplicity",
    "valuerange": [
        1,
        2
    ],
    "samplerfunc": "self.const_int(1, 2, 2)",
    "precode": "self.population_options[\"multiplicity\"] = multiplicity; self.bse_options[\"multiplicity\"] = multiplicity; options={'JSON': None, 'q_high_extrapolation_method': 'flat', 'multiplicity_model': 'Poisson', 'IMF_distribution': 'kroupa2001', 'q_low_extrapolation_method': 'flat', 'load data': True, 'clean cache': False, 'apply settings': True, 'clean load flag': False, 'clean all': False, 'setup grid': True, 'multiplicity': 2, 'samplerfuncs': {'ecc': [None, None, None], 'logP': [None, None, None], 'M': ['self.const_linear(math.log(0.08), math.log(80.0), 4)', None, None, None]}, 'multiplicity_modulator': [1, 1, 0, 0], 'Mmin': 0.08, 'resolutions': {'M': [4, 4, 0, 0], 'logP': [4, 0, 0], 'ecc': [0, 0, 0]}, 'normalize_multiplicities': 'merge', 'ranges': {'ecc': [0.0, 0.99], 'q': [None, None], 'M': [0.08, 80.0], 'logP': [0.0, 8.0]}}",
    "postcode": null,
    "probdist": 1,
    "dphasevol": -1,
    "condition": "([1, 1, 0, 0][int(multiplicity)-1] > 0)",
    "gridtype": "discrete",
    "branchpoint": 0,
    "branchcode": null,
    "topcode": null,
    "bottomcode": null,
    "sampling_variable_number": 0,
    "dry_parallel": false,
    "dependency_variables": null
}
Added sampling variable: {
    "name": "lnM_1",
    "parameter_name": "M_1",
    "longname": "Primary mass",
    "valuerange": [
        "np.log(0.08)",
        "np.log(80.0)"
    ],
    "samplerfunc": "self.const_linear(math.log(0.08), math.log(80.0), 4)",
    "precode": "M_1 = np.exp(lnM_1); options[\"M_1\"]=M_1",
    "postcode": null,
    "probdist": "self.Moe_di_Stefano_2017_pdf({'JSON': None, 'q_high_extrapolation_method': 'flat', 'multiplicity_model': 'Poisson', 'IMF_distribution': 'kroupa2001', 'q_low_extrapolation_method': 'flat', 'load data': True, 'clean cache': False, 'apply settings': True, 'clean load flag': False, 'clean all': False, 'setup grid': True, 'multiplicity': 2, 'samplerfuncs': {'ecc': [None, None, None], 'logP': [None, None, None], 'M': ['self.const_linear(math.log(0.08), math.log(80.0), 4)', None, None, None]}, 'multiplicity_modulator': [1, 1, 0, 0], 'Mmin': 0.08, 'resolutions': {'M': [4, 4, 0, 0], 'logP': [4, 0, 0], 'ecc': [0, 0, 0]}, 'normalize_multiplicities': 'merge', 'ranges': {'ecc': [0.0, 0.99], 'q': [None, None], 'M': [0.08, 80.0], 'logP': [0.0, 8.0]}, 'multiplicity': multiplicity, 'M_1': M_1}, verbosity=self.population_options['verbosity'])['total_probdens'] if multiplicity == 1 else 1",
    "dphasevol": "dlnM_1",
    "condition": null,
    "gridtype": "centred",
    "branchpoint": 1,
    "branchcode": "multiplicity == 1",
    "topcode": null,
    "bottomcode": null,
    "sampling_variable_number": 1,
    "dry_parallel": false,
    "dependency_variables": null
}
Added sampling variable: {
    "name": "log10per",
    "parameter_name": "orbital_period",
    "longname": "log10(Orbital_Period)",
    "valuerange": [
        0.0,
        8.0
    ],
    "samplerfunc": "self.const_linear(0.0, 8.0, 4)",
    "precode": "orbital_period = 10.0**log10per\nqmin=0.08/M_1\nqmax=maximum_mass_ratio_for_RLOF(M_1, orbital_period)\n",
    "postcode": null,
    "probdist": 1.0,
    "dphasevol": "(0.43429448190325176 * dlog10per)",
    "condition": "(self.population_options[\"multiplicity\"] >= 2)",
    "gridtype": "centred",
    "branchpoint": 0,
    "branchcode": null,
    "topcode": null,
    "bottomcode": null,
    "sampling_variable_number": 2,
    "dry_parallel": false,
    "dependency_variables": null
}
Added sampling variable: {
    "name": "q",
    "parameter_name": "M_2",
    "longname": "Mass ratio",
    "valuerange": [
        null,
        null
    ],
    "samplerfunc": "self.const_linear(0.08/M_1, qmax, 4)",
    "precode": "\nM_2 = q * M_1\nsep = calc_sep_from_period(M_1, M_2, orbital_period)\n    ",
    "postcode": null,
    "probdist": 1,
    "dphasevol": "dq",
    "condition": null,
    "gridtype": "centred",
    "branchpoint": 0,
    "branchcode": null,
    "topcode": null,
    "bottomcode": null,
    "sampling_variable_number": 3,
    "dry_parallel": false,
    "dependency_variables": null
}

Set ensemble options

Next we should set up the ensemble options.

[3]:
# Ensemble options
population.set(
    # Turn on the ensemble
    ensemble = True,

    # we want to defer output of the ensemble
    # until the final star is run
    ensemble_defer = True,

    # we want to use log10(time)  bins


    # time resolution
    # we output to the ensemble in either linear or logarithmic time: you
    # can set ensemble_logtimes to False for linear, True for logarithmic
    ensemble_logtimes = True,

    # and then you need to choose the time resolution
    ensemble_dt = 1000.0, # linear time resolution (used if logtimes==False, i.e. not in this example)
    ensemble_logdt = 0.1, # log10(time) resolution in dex (used if logtimes==True, i.e. in this example)

    # because we use log10(time) we cannot start measuring time at time=0
    # but should start soon after, here at 0.1 Myr
    ensemble_startlogtime = 0.1,

    # next we select which ensembles should be output
    #
    # We do not want to output all of them, so first turn them all off
    ensemble_filters_off = 1, # turn off all filters except...

    # then turn on those ensembles that you require
    #
    # The output for each is defined in binary_c in the function
    # ensemble_log() found in src/ensemble/ensemble_log.c
    #
    # and you can find a full list of filters in
    # src/ensemble/ensemble.def
    # (they are not all included here)
    #
    ensemble_filter_CHEMICAL_YIELDS = False, # require chemical yields
    ensemble_filter_SCALARS = True, # required scalar number counts
    ensemble_filter_TEST = False, # include test data
    ensemble_filter_INITIAL_DISTRIBUTIONS = False, # initial distributions

)

Next we evolve the population. Please wait for it to finish: this will take longer if you have set the resolution to be a larger number.

[4]:
# Evolve grid
population.evolve()
Warning: No parse function set. Make sure you intended to do this.
Write grid code to /tmp/binary_c_python-david/notebooks/notebook_ensembles/binary_c_grid_3b8635f60a5a4056a5a94f6bf1c367d2.py [dry_run = True]
Doing a dry run of the grid.
Grid has handled 30 stars with a total probability of 0.981585

**********************************
*             Dry run            *
*      Total starcount is 30     *
*  Total probability is 0.981585 *
**********************************

setting up the system_queue_filler now
Write grid code to /tmp/binary_c_python-david/notebooks/notebook_ensembles/binary_c_grid_3b8635f60a5a4056a5a94f6bf1c367d2.py [dry_run = False]
Signalling processes to stop

****************************************************
*                Process 0 finished:               *
*  generator started at 2023-05-19T11:35:22.549022 *
* generator finished at 2023-05-19T11:35:26.719666 *
*                   total: 4.17s                   *
*           of which 4.06s with binary_c           *
*                  Ran 30 systems                  *
*       with a total probability of 0.981585       *
*         This thread had 0 failing systems        *
*       with a total failed probability of 0       *
*   Skipped a total of 3 zero-probability systems  *
*                                                  *
****************************************************


**********************************************************
*  Population-3b8635f60a5a4056a5a94f6bf1c367d2 finished! *
*           The total probability is 0.981585.           *
*  It took a total of 4.57s to run 30 systems on 1 cores *
*                   = 4.57s of CPU time.                 *
*              Maximum memory use 173.703 MB             *
**********************************************************

No failed systems were found in this run.
[4]:
{'population_id': '3b8635f60a5a4056a5a94f6bf1c367d2',
 'evolution_type': 'grid',
 'failed_count': 0,
 'failed_prob': 0,
 'failed_systems_error_codes': [],
 'errors_exceeded': False,
 'errors_found': False,
 'total_probability': 0.9815845963697204,
 'total_count': 30,
 'start_timestamp': 1684492522.5212972,
 'end_timestamp': 1684492527.0914335,
 'time_elapsed': 4.570136308670044,
 'total_mass_run': 408.2548715232803,
 'total_probability_weighted_mass_run': 0.7680948022521784,
 'zero_prob_stars_skipped': 3}

How to handle ensemble data

Ensemble data is stored in a dict in the population object, at

ensemble = population.grid_ensemble_results['ensemble']

One can loop over keys in the dict directly, or, as shown below, convert to JSON and output in a format that is both human readable and computer processable.

[5]:
# convert the ensemble results to pretty JSON output
# and show this
import json

ensemble = population.grid_ensemble_results['ensemble']
json_buffer = json.dumps(ensemble,
                         sort_keys = True,
                         indent = True,
                         ensure_ascii = True);

print("Printing abridged output: ")
print('\n'.join(json_buffer.splitlines()[:200]))
print("...")
Printing abridged output:
{
 "scalars": {
  "BINARY_BLUE_STRAGGLER": {
   "-0.1": 5100.0,
   "-0.2": 5100.0,
   "-0.3": 5100.0,
   "-0.4": 5100.0,
   "-0.5": 5100.0,
   "-0.6": 5100.0,
   "-0.7": 5100.0,
   "-0.8": 5100.0,
   "-0.9": 5100.0,
   "0": 5100.0,
   "0.1": 5100.0,
   "0.2": 5100.0,
   "0.3": 5100.0,
   "0.4": 5100.0,
   "0.5": 5100.0,
   "0.6": 5100.0,
   "0.7": 5100.0,
   "0.8": 5100.0,
   "0.9": 5100.0,
   "1": 4924.577876078783,
   "1.1": 4926.172884057682,
   "1.2": 5016.719954573364,
   "1.3": 5100.0,
   "1.4": 5100.0,
   "1.5": 5100.0,
   "1.6": 5100.0,
   "1.7": 5100.0,
   "1.8": 5100.0,
   "1.9": 5100.0,
   "2": 5100.0,
   "2.1": 5100.0,
   "2.2": 5100.0,
   "2.3": 5100.0,
   "2.4": 5100.0,
   "2.5": 5100.0,
   "2.6": 5100.0,
   "2.7": 5100.0,
   "2.8": 4954.0147194797655,
   "2.9": 5100.0,
   "3": 5100.0,
   "3.1": 5100.0,
   "3.2": 5100.0,
   "3.3": 5100.0,
   "3.4": 5100.0,
   "3.5": 5100.0,
   "3.6": 5100.0,
   "3.7": 5100.0,
   "3.8": 5100.0,
   "3.9": 5100.0,
   "4": 5100.0,
   "4.1": 5100.0,
   "4.2": 5100.0
  },
  "BLUE_STRAGGLER": {
   "1": 0.0019199736971710975,
   "1.1": 0.0019359479374287001,
   "1.2": 0.0006362375548468333
  },
  "BSG": {
   "-0.1": 0.005607909982917069,
   "-0.2": 0.005607909982917072,
   "-0.3": 0.005607909982917073,
   "-0.4": 0.005607909982917071,
   "-0.5": 0.005607909982917072,
   "-0.6": 0.005607909982917072,
   "-0.7": 0.005607909982917072,
   "-0.8": 0.005607909982917073,
   "0": 0.005607909982917073,
   "0.1": 0.005607909982917073,
   "0.2": 0.005607909982917073,
   "0.3": 0.0056079099829170745,
   "0.4": 0.0056079099829170745,
   "0.5": 0.0056079099829170745,
   "0.6": 0.005607909982917068,
   "0.7": 0.005607909982917066,
   "0.8": 0.0061711582707625395,
   "0.9": 0.0065034468983274545,
   "1": 0.0019678502946268005,
   "1.1": 0.0019359479374287001,
   "1.2": 0.0006399995173114125
  },
  "BSG_LUM": {
   "-0.1": 465.5191651818834,
   "-0.2": 460.392224832812,
   "-0.3": 456.29583301972133,
   "-0.4": 453.27348250770524,
   "-0.5": 450.9316215895541,
   "-0.6": 448.84251185892276,
   "-0.7": 447.2064146771188,
   "-0.8": 445.92144094877807,
   "0": 472.3649148942722,
   "0.1": 481.22334926909423,
   "0.2": 493.11815327706427,
   "0.3": 509.21177670859254,
   "0.4": 531.2262266075203,
   "0.5": 562.0440342082067,
   "0.6": 606.035936673939,
   "0.7": 670.6063814725621,
   "0.8": 815.6337342686954,
   "0.9": 947.4237842712193,
   "1": 359.23555902016886,
   "1.1": 414.81910251305214,
   "1.2": 123.39250913219956
  },
  "CHeB": {
   "0.9": 2.793935424837457e-05,
   "1": 0.0013954712827206621,
   "1.2": 0.0002922201248659066,
   "1.3": 7.638832761615915e-05,
   "2.2": 0.0005587423713012249,
   "2.3": 0.00016537042412094138,
   "2.8": 0.017182152365608324,
   "2.9": 0.08441693085167577,
   "3": 0.0053641789323110155,
   "3.1": 0.0011526407204140866,
   "3.6": 0.0009091782936834672,
   "3.7": 0.0005332024348221244
  },
  "CHeB_LUM": {
   "0.9": 4.904275360104634,
   "1": 282.57147576989627,
   "1.2": 54.713436143298615,
   "1.3": 4.264579923259343,
   "2.2": 0.4665408495224178,
   "2.3": 0.1815348358535978,
   "2.8": 1.9254600469031078,
   "2.9": 9.075661993123175,
   "3": 0.4430953434467815,
   "3.1": 0.08133881555683144,
   "3.6": 0.07117273008614428,
   "3.7": 0.05923231245679924
  },
  "COMENV_DETACHED": {
   "1.2": 0.0002530722287064908
  },
  "COMENV_GB_MERGER": {
   "2.8": 0.00012544608346379797
  },
  "COMENV_HG_DETACHED": {
   "1.2": 8.18710741450789e-10
  },
  "COMENV_HG_MERGER": {
   "0.9": 0.0008770919833338064
  },
  "COMENV_MERGER": {
   "0.9": 0.0008770919833338064,
   "2.8": 0.00012544608346379797
  },
  "COWD": {
   "2.3": 0.0008721169883882842,
   "2.4": 0.001085382741463638,
   "2.5": 0.0010853827414636379,
   "2.6": 0.0010853827414636379,
   "2.7": 0.0010853827414636379,
   "2.8": 0.0010853827414636379,
   "2.9": 0.012822671114861373,
   "3": 0.10087960710054318,
   "3.1": 0.10625692062895202,
   "3.2": 0.10748718320385917,
   "3.3": 0.10751810622239019,
   "3.4": 0.10751810622239011,
   "3.5": 0.10751810622239019,
   "3.6": 0.10751810622239255,
   "3.7": 0.11481008057357076,
   "3.8": 0.11739309502819884,
   "3.9": 0.11739309502819867,
   "4": 0.11739309502819842,
   "4.1": 0.11739309502819852,
   "4.2": 0.11739309502819908
  },
  "COWD_LUM": {
   "2.3": 1.778572584242028e-05,
   "2.4": 5.3756640859410884e-06,
   "2.5": 2.4216346987451443e-06,
   "2.6": 1.3150602881568674e-06,
   "2.7": 7.854908743566147e-07,
   "2.8": 4.96518362138202e-07,
   "2.9": 0.0009260738742458075,
   "3": 0.0001726658668669032,
   "3.1": 7.730821627291823e-05,
   "3.2": 2.081983661385333e-05,
   "3.3": 1.0645373457720407e-05,
   "3.4": 6.386100489734644e-06,
   "3.5": 4.05214265513937e-06,
   "3.6": 2.6680198277019643e-06,
   "3.7": 1.471450044787078e-05,
   "3.8": 1.7579410675990394e-06,
   "3.9": 1.0988367129444205e-06,
   "4": 7.399876975417793e-07,
   "4.1": 5.184110995272241e-07,
   "4.2": 3.657711355308934e-07
  },
  "DAVIES_RSG": {
   "0.9": 2.48073249154573e-05,
   "1": 0.0013806237504989608,
   "1.2": 0.00028985004324987647,
   "1.3": 8.145371575199544e-05,
...

One can write the grid_ensemble_results output directly to a file, e.g.,

population.write_ensemble(filename,
                          sort_keys=True,
                          indent=4)

If your file ends in .bz2 or .gz it is automatically compressed using either the bzip2 or gzip algorithm, respectively.

Scalars

We wanted to access the “scalars” part of the ensemble. This is a set of scalar properties of the stellar population that are a function of, in our case, log10(time).

The scalars are found in ensemble['scalars'] which we can output.

[6]:
json_buffer = json.dumps(ensemble['scalars'],
                         sort_keys = True,
                         indent = True,
                         ensure_ascii = True)

print("Printing abridged output: ")
print('\n'.join(json_buffer.splitlines()[:200]))
print("...")
Printing abridged output:
{
 "BINARY_BLUE_STRAGGLER": {
  "-0.1": 5100.0,
  "-0.2": 5100.0,
  "-0.3": 5100.0,
  "-0.4": 5100.0,
  "-0.5": 5100.0,
  "-0.6": 5100.0,
  "-0.7": 5100.0,
  "-0.8": 5100.0,
  "-0.9": 5100.0,
  "0": 5100.0,
  "0.1": 5100.0,
  "0.2": 5100.0,
  "0.3": 5100.0,
  "0.4": 5100.0,
  "0.5": 5100.0,
  "0.6": 5100.0,
  "0.7": 5100.0,
  "0.8": 5100.0,
  "0.9": 5100.0,
  "1": 4924.577876078783,
  "1.1": 4926.172884057682,
  "1.2": 5016.719954573364,
  "1.3": 5100.0,
  "1.4": 5100.0,
  "1.5": 5100.0,
  "1.6": 5100.0,
  "1.7": 5100.0,
  "1.8": 5100.0,
  "1.9": 5100.0,
  "2": 5100.0,
  "2.1": 5100.0,
  "2.2": 5100.0,
  "2.3": 5100.0,
  "2.4": 5100.0,
  "2.5": 5100.0,
  "2.6": 5100.0,
  "2.7": 5100.0,
  "2.8": 4954.0147194797655,
  "2.9": 5100.0,
  "3": 5100.0,
  "3.1": 5100.0,
  "3.2": 5100.0,
  "3.3": 5100.0,
  "3.4": 5100.0,
  "3.5": 5100.0,
  "3.6": 5100.0,
  "3.7": 5100.0,
  "3.8": 5100.0,
  "3.9": 5100.0,
  "4": 5100.0,
  "4.1": 5100.0,
  "4.2": 5100.0
 },
 "BLUE_STRAGGLER": {
  "1": 0.0019199736971710975,
  "1.1": 0.0019359479374287001,
  "1.2": 0.0006362375548468333
 },
 "BSG": {
  "-0.1": 0.005607909982917069,
  "-0.2": 0.005607909982917072,
  "-0.3": 0.005607909982917073,
  "-0.4": 0.005607909982917071,
  "-0.5": 0.005607909982917072,
  "-0.6": 0.005607909982917072,
  "-0.7": 0.005607909982917072,
  "-0.8": 0.005607909982917073,
  "0": 0.005607909982917073,
  "0.1": 0.005607909982917073,
  "0.2": 0.005607909982917073,
  "0.3": 0.0056079099829170745,
  "0.4": 0.0056079099829170745,
  "0.5": 0.0056079099829170745,
  "0.6": 0.005607909982917068,
  "0.7": 0.005607909982917066,
  "0.8": 0.0061711582707625395,
  "0.9": 0.0065034468983274545,
  "1": 0.0019678502946268005,
  "1.1": 0.0019359479374287001,
  "1.2": 0.0006399995173114125
 },
 "BSG_LUM": {
  "-0.1": 465.5191651818834,
  "-0.2": 460.392224832812,
  "-0.3": 456.29583301972133,
  "-0.4": 453.27348250770524,
  "-0.5": 450.9316215895541,
  "-0.6": 448.84251185892276,
  "-0.7": 447.2064146771188,
  "-0.8": 445.92144094877807,
  "0": 472.3649148942722,
  "0.1": 481.22334926909423,
  "0.2": 493.11815327706427,
  "0.3": 509.21177670859254,
  "0.4": 531.2262266075203,
  "0.5": 562.0440342082067,
  "0.6": 606.035936673939,
  "0.7": 670.6063814725621,
  "0.8": 815.6337342686954,
  "0.9": 947.4237842712193,
  "1": 359.23555902016886,
  "1.1": 414.81910251305214,
  "1.2": 123.39250913219956
 },
 "CHeB": {
  "0.9": 2.793935424837457e-05,
  "1": 0.0013954712827206621,
  "1.2": 0.0002922201248659066,
  "1.3": 7.638832761615915e-05,
  "2.2": 0.0005587423713012249,
  "2.3": 0.00016537042412094138,
  "2.8": 0.017182152365608324,
  "2.9": 0.08441693085167577,
  "3": 0.0053641789323110155,
  "3.1": 0.0011526407204140866,
  "3.6": 0.0009091782936834672,
  "3.7": 0.0005332024348221244
 },
 "CHeB_LUM": {
  "0.9": 4.904275360104634,
  "1": 282.57147576989627,
  "1.2": 54.713436143298615,
  "1.3": 4.264579923259343,
  "2.2": 0.4665408495224178,
  "2.3": 0.1815348358535978,
  "2.8": 1.9254600469031078,
  "2.9": 9.075661993123175,
  "3": 0.4430953434467815,
  "3.1": 0.08133881555683144,
  "3.6": 0.07117273008614428,
  "3.7": 0.05923231245679924
 },
 "COMENV_DETACHED": {
  "1.2": 0.0002530722287064908
 },
 "COMENV_GB_MERGER": {
  "2.8": 0.00012544608346379797
 },
 "COMENV_HG_DETACHED": {
  "1.2": 8.18710741450789e-10
 },
 "COMENV_HG_MERGER": {
  "0.9": 0.0008770919833338064
 },
 "COMENV_MERGER": {
  "0.9": 0.0008770919833338064,
  "2.8": 0.00012544608346379797
 },
 "COWD": {
  "2.3": 0.0008721169883882842,
  "2.4": 0.001085382741463638,
  "2.5": 0.0010853827414636379,
  "2.6": 0.0010853827414636379,
  "2.7": 0.0010853827414636379,
  "2.8": 0.0010853827414636379,
  "2.9": 0.012822671114861373,
  "3": 0.10087960710054318,
  "3.1": 0.10625692062895202,
  "3.2": 0.10748718320385917,
  "3.3": 0.10751810622239019,
  "3.4": 0.10751810622239011,
  "3.5": 0.10751810622239019,
  "3.6": 0.10751810622239255,
  "3.7": 0.11481008057357076,
  "3.8": 0.11739309502819884,
  "3.9": 0.11739309502819867,
  "4": 0.11739309502819842,
  "4.1": 0.11739309502819852,
  "4.2": 0.11739309502819908
 },
 "COWD_LUM": {
  "2.3": 1.778572584242028e-05,
  "2.4": 5.3756640859410884e-06,
  "2.5": 2.4216346987451443e-06,
  "2.6": 1.3150602881568674e-06,
  "2.7": 7.854908743566147e-07,
  "2.8": 4.96518362138202e-07,
  "2.9": 0.0009260738742458075,
  "3": 0.0001726658668669032,
  "3.1": 7.730821627291823e-05,
  "3.2": 2.081983661385333e-05,
  "3.3": 1.0645373457720407e-05,
  "3.4": 6.386100489734644e-06,
  "3.5": 4.05214265513937e-06,
  "3.6": 2.6680198277019643e-06,
  "3.7": 1.471450044787078e-05,
  "3.8": 1.7579410675990394e-06,
  "3.9": 1.0988367129444205e-06,
  "4": 7.399876975417793e-07,
  "4.1": 5.184110995272241e-07,
  "4.2": 3.657711355308934e-07
 },
 "DAVIES_RSG": {
  "0.9": 2.48073249154573e-05,
  "1": 0.0013806237504989608,
  "1.2": 0.00028985004324987647,
  "1.3": 8.145371575199544e-05,
  "2.2": 7.528383881549066e-05,
...

Let’s now output the number of hydrogen-shell burning red giant stars as a function of time.

Note that we have to be careful here: ensemble dictionary keys are always stored as strings so if we want to convert the log10(time) to time, we have to first convert the string to a float.

[7]:
stellar_type = 'GB'
for time_string in ensemble['scalars']['GB']:
    time = float(time_string)
    print(f"{10**time:20g} {ensemble['scalars']['GB'][time_string]:20g}")
             158.489          1.65257e-05
             630.957           0.00418894
             794.328          0.000298925
                1000          6.96132e-05
             1258.93          1.70586e-05
             3981.07           0.00213166
             5011.87           0.00089738

Mass in stars

One can access the total mass formed into stars in the population using the following code. This is sometimes useful because if you want to generate data appropriate to galactic-chemical evolution you probably want these data to be per unit mass into stars formed.

[8]:
mass_in_stars = population.grid_ensemble_results["metadata"]["total_probability_weighted_mass"]
print(f"mass in stars {mass_in_stars}")
mass in stars 0.7680948022521784